﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace XYZShell.Helper
{

	/// <summary>
	/// 参数解析
	/// </summary>
    public class ArgumentParser
    {

        ArgumentItemCollection _args;
        public ArgumentParser(string description = "", string prefix = "-")
        {
            Description = description;
            _args = new ArgumentItemCollection();
            PrefixChars = prefix;
        }

        public object this[string name]
        {
            get { return _args[name]; }
        }
        //public static  ArgumentParser Parse(params  string args)
        //{
        //    ArgumentParser rtn = new ArgumentParser();
        //    return rtn;
        //}
        public string ProgramName
        {
            get;
            set;
        }

        public string Description
        {
            get;
            set;
        }
        public string Footer
        {
            get;
            set;
        }
        public static string PrefixChars
        {
            get;
            set;
        }

        public bool AddHelp
        {
            get;
            set;
        }

        /// <summary>
        /// 添加开关
        /// </summary>
        /// <param name="flags"></param>
        /// <param name="helpMsg"></param>
        /// <param name="required"></param>
        public void AddArgument(string[] flags, string helpMsg = "", bool required = false)
        {
            ArgumentItem arg = new ArgumentItem
            {
                Names = flags,
                HelpMessage = helpMsg,
                Required = required,
                ArgumentType = ArgumentItemType.Flag,
                Value = false,
                DataType = typeof(bool),

            };
            _args.Add(arg);
        }
		public void AddArgument( ArgumentItem item )
		{
			_args.Add( item );
		}
        /// <summary>
        /// 添加参数
        /// </summary>
        /// <param name="names"></param>
        /// <param name="dest"></param>
        /// <param name="dataType"></param>
        /// <param name="helpMsg"></param>
        /// <param name="required"></param>
        public void AddArgument(string[] names, string dest, Type dataType, string helpMsg = "", bool required = false)
        {
            ArgumentItem arg = new ArgumentItem
            {
                Names = names,
                Dest = dest,
                DataType = dataType,
                HelpMessage = helpMsg,
                Required = required,
                ArgumentType = ArgumentItemType.KeyValue
            };
            _args.Add(arg);
        }
        /// <summary>
        /// 添加参数,选择模式
        /// </summary>
        /// <param name="names"></param>
        /// <param name="dest"></param>
        /// <param name="dataType"></param>
        /// <param name="choices"></param>
        /// <param name="helpMsg"></param>
        /// <param name="required"></param>
        public void AddArgument(string[] names, string dest, Type dataType, string[] choices, string helpMsg = "", bool required = false)
        {
            ArgumentItem arg = new ArgumentItem
            {
                Names = names,
                Dest = dest,
                DataType = dataType,
                Choices = choices,
                HelpMessage = helpMsg,
                Required = required,
                ArgumentType = ArgumentItemType.KeyValue
            };
            _args.Add(arg);
        }
        /// <summary>
        /// 添加模式,带默认值
        /// </summary>
        /// <param name="names"></param>
        /// <param name="dest"></param>
        /// <param name="dataType"></param>
        /// <param name="defaultValue"></param>
        /// <param name="helpMsg"></param>
        /// <param name="required"></param>
        public void AddArgument(string[] names, string dest, Type dataType, object defaultValue, string helpMsg = "", bool required = false)
        {
            ArgumentItem arg = new ArgumentItem
            {
                Names = names,
                Dest = dest,
                DataType = dataType,
                Default = defaultValue,
                Value = defaultValue,
                HelpMessage = helpMsg,
                Required = required,
                ArgumentType = ArgumentItemType.KeyValue
            };
            _args.Add(arg);
        }

        void error(string msg)
        {
            Console.WriteLine(msg);
        }

        public string Usage()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("usage:{0} {1}\r\n", ProgramName, buildArgLine());
            sb.AppendLine(Description);
            sb.AppendLine("optional arguments:");
            foreach (var a in _args)
            {
                buildArgItemString(sb, a);
            }
            if (string.IsNullOrEmpty(Footer))
            {
                //sb.AppendLine();
                sb.AppendLine(Footer);
            }
            Console.Write(sb.ToString());
            return sb.ToString();
        }

        public bool Parse(string[] args)
        {
			//if (args.Length == 0)
			//{
			//	Usage();
			//	return false;
			//}
            List<string> arglst = new List<string>();

            foreach (var arg in args)
            {
                var a = arg;
                if (arg.Contains(" "))
                {
                    
                    a = string.Format("\"{0}\"",a);

                }
                arglst.Add(a);
                
            }
            return Parse(arglst.ToArray().JoinString(" "));
        }
        public bool Parse(string argline)
        {

            foreach (var a in _args)
            {
                string p = a.GetPatter();
                Regex rgx = new Regex(p);
                bool b = rgx.IsMatch(argline);
                if (!b && a.Required)
                {
                    error(string.Format("Arg {0} be required", a.Names[1]));
                    Usage();
                    return false;
                }
                if (b)
                {
                    var m = rgx.Match(argline);
                    
                    string rs = "${" + string.Format("s{0}", a.Names[0]) + "}";
                    string rl = "${" + string.Format("l{0}", a.Names[0]) + "}";
                    string rv = "${" + string.Format("v{0}", a.Names[0]) + "}";
                    var sht = m.Result(rs);
                    var lng = m.Result(rl);
                    var val = m.Result(rv).Trim('"');
                    switch (a.ArgumentType)
                    {
                        case ArgumentItemType.Flag:
                            a.Value = true;
                            break;
                        case ArgumentItemType.KeyValue:
                            if (val != "")
                            {
                                a.Value = Convert.ChangeType(val, a.DataType);

                            }
                            else
                            {
                                if(a.Default==null)
                                {
                                    //todo: error;
                                }
                            }
                            break;
                    }


                }
            }
            return true;
        }
        string buildArgLine()
        {
            return "";
        }
        void buildArgItemString(StringBuilder sb, ArgumentItem item)
        {
			int padright=15;
			int breaklen=74 - padright;
            switch (item.ArgumentType)
            {
                case ArgumentItemType.Flag:
                    string sf = string.Format("  {0}{1}, {0}{0}{2}", PrefixChars, item.Names[0], item.Names[1]);

                    var hs = (item.Choices != null && item.Choices.Length > 0 ? string.Format(" {0} {1}.", item.HelpMessage, item.Choices.JoinString(",")) : item.HelpMessage);
                    hs = item.Default != null ? hs + string.Format("{0} {1} by default.", hs, item.Default) : hs;
					var hsa = hs.BreakWord( breaklen );
					sb.AppendLine( sf.PadRight( padright ) +"; "+ hsa[0] );
                    for (int i = 1; i < hsa.Length; i++)
                    {
						sb.AppendLine( "".PadLeft( padright ) + hsa[i] );
                    }
                    break;
                case ArgumentItemType.KeyValue:
                    sb.AppendFormat("  {0}{1} {2}, {0}{0}{3} {2}\r\n", PrefixChars, item.Names[0], buildDestString(item), item.Names[1]);
                    hs = (item.Choices != null && item.Choices.Length > 0 ? string.Format(" {0} {1}.", item.HelpMessage, item.Choices.JoinString(",")) : item.HelpMessage);
                    hs = item.Default != null ? hs + string.Format("{0} {1} by default.", hs, item.Default) : hs;
					hsa = hs.BreakWord( breaklen );
                    foreach (var s in hsa)
                    {
						sb.AppendLine( "".PadLeft( padright ) +s );
                    }
                    break;
            }
        }

        string buildDestString(ArgumentItem item)
        {
            if (item.Default != null)
                return string.Format("[{0}]", item.Dest.ToUpper());
            else
                return item.Dest.ToUpper();
        }

        public class ArgumentItemCollection : Collection<ArgumentItem>
        {
            public ArgumentItemCollection()
                : base()
            { }
            public ArgumentItemCollection(IList<ArgumentItem> list)
                : base(list)
            { }

            public object this[string name]
            {
                get
                {
                    foreach (var o in Items)
                    {
                        foreach (var s in o.Names)
                        {
                            if (s == name)
                                return o.Value;
                        }
                    }
                    return null;
                }
            }
        }
    }
    public class ArgumentItem
    {
		public ArgumentItem()
		{
			this.DataType = typeof( string );
		}
        public string[] Names
        {
            get;
            set;
        }
        public ArgumentItemType ArgumentType
        {
            get;
            set;
        }
        public Type DataType
        {
            get;
            set;
        }
        public object Value
        {
            get;
            set;
        }

        public string HelpMessage
        {
            get;
            set;
        }
        public object Default
        {
            get;
            set;
        }
        public bool Required
        {
            get;
            set;
        }
        public string Dest
        {
            get;
            set;
        }
        public string[] Choices
        {
            get;
            set;
        }

        public string GetPatter()
        {

            string sht = string.Format(@"(\{0}(?<{2}>{1}))", ArgumentParser.PrefixChars, Names[0], "s" + Names[0]);
            string lng = string.Format(@"(\{0}\{0}(?<{2}>{1}))", ArgumentParser.PrefixChars, Names[1], "l" + Names[0]);
            string v = string.Format("({0}|{1})", sht, lng);
            if (ArgumentType == ArgumentItemType.KeyValue)
            {
                if (Choices != null && Choices.Length > 0)
                {

                    v = string.Format("{0}(\\s+(?<{1}>({2})))", v, "v" + Names[0], Choices.JoinString("|"));
                }
                else
                {
                    //v = string.Format("{0}(\\s+(?<{1}>(\".+\")|\\w+\\b))", v, "v" + Names[0]);
                    v = string.Format("{0}(\\s+(?<{1}>(\".+\")|.+?(\\s|\\Z)))", v, "v" + Names[0]);
                }
                if (Default != null)
                {
                    v = v + "?";
                }
            }
            return v;

        }
    }

    public enum ArgumentItemType
    {
        KeyValue,
        Flag
    }

}
